Java 기반의 경량 애플리케이션 프레임워크
애플리케이션 개발 시 반복되는 객체 생성, 관리, 트랜잭션 처리, 공통 관심사 처리 등을 쉽게 해줌
개발자가 직접 "객체 생성, 의존성 관리, 트랜잭션 관리, AOP 등"을 구현할 필요 없이 Spring이 대신 처리
Spring이 제공하는 핵심기능
- IoC/DI : 객체 생성과 의존성 주입을 자동화
- AOP(Aspect-Oriented Programming) : 로깅, 트랜잭션, 보안 등 공통 관심사 분리
- 트랜잭션 관리 : 선언적 트랜잭션(@Transactional)
- MVC 웹 프레임워크 : Spring MVC
- 데이터 접근 편의 : Spring Data, JDBC Template
- 테스트 지원 : Mock Bean 주입, 단위 테스트 용이
Spring 사용 이유
- 객체 관리와 결합도 낮추기
- 전통적인 Java는
new로 직접 객체 생성 -> 의존성 강함 -> 테스트 어려움, 유지보수 힘듬- Spring은 IoC/DI로 객체 생성/주입을 Spring이 담당하기 때문에 클래스 간 결합도가 낮음
- 반복 코드 최소화
- 트랜잭션, 예외 처리, 로깅 등을 매번 작성하지 않아도 됨 -> 코드 간결
- 확장성과 테스트 용이성
- DI 덕분에 Mock 객체 주입 가능 -> 단위 테스트 편리
- AOP 덕분에 공통 로직 쉽게 적용 -> 기능 추가/ 변경 유연
- 유지보수 & 재사용성
- Bean과 공통 로직 분리 -> 기능 확장, 코드 재사용 간단
누가 객체를 만들고 관리할지를 프레임워크에 맡기는 것IoC의 구체적인 방법 중 하나로, 필요한 객체를 외부에서 주입하는 것
ex :
@Service public class UserService { private final UserRepository userRepository; @Autowired // DI: Spring이 UserRepository를 넣어줌 public UserService(UserRepository userRepository) { this.userRepository = userRepository; } }
주입 방식
- 생성자 주입 -> 가장 권장, 불변성을 보장하고 테스트 용이
- 예시
@Service public class UserService { private final UserRepository userRepository; // 생성자를 통해 의존성 주입 @Autowired // Spring 4.3 이후 생략 가능, 단 생성자가 하나일 경우 public UserService(UserRepository userRepository) { this.userRepository = userRepository; } }
- 특징 :
- 불변성 보장 : final 필드 사용 가능
- 테스트 용이 : Mock 주입 가능
- 순환 의존성 발생 시 컴파일 시점에서 오류
- 동작 :
- Spring이
UserRepositoryBean을 찾아 생성UserService생성자 호출 시 자동으로 주입UserService완전 초기화
- 필드 주입 -> 편리하지만 테스트가 어려움
- 예시
@Service public class UserService { @Autowired private UserRepository userRepository; // Spring이 직접 주입 }
- 특징
- 코드가 간단 : 선언만 하면 됨
- 테스트가 어려움 : private 필드이므로 Mock 주입 시 Reflection 필요
- 순환 의존성 해결 어려움
@PostConstruct가 선언된 메서드 이후부터는 필드 사용 가능
- 동작
- Spring이 Bean 생성 후 Reflection을 통해 필드에 의존성 주입
- 생성자에서는 아직 주입되지 않음 -> 초기화 시점 주의 필요
- Setter 주입 -> 선택적 의존성 주입 가능
- 예시
@Service public class UserService { private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } }
- 특징
- 선택적 의존성 주입 가능 (null 허용)
- 테스트 용이 : Mock 주입 가능
- 순환 의존성 일부 해결 가능
- 동작
- Spring이 Bean 생성
- Setter 메서드를 호출하며 의존성 주입
Spring 컨테이너가 관리하는 객체
Spring이 생성, 초기화, 의존성 주입, 소멸까지 관리하는 객체
@Component, @Service, @Repository, @Controller 등 어노테이션을 통해 Bean 등록
Spring이 컨테이너 시작 시 이 클래스 객체를 생성하고 관리
Bean 등록 방법
- 어노테이션 기반
@Component @Service @Repository @Controller
- Java Config
@Configuration public class AppConfig { @Bean public UserService userService() { return new UserService(userRepository()); } @Bean public UserRepository userRepository() { return new UserRepository(); } }
- XML 기반 (과거)
<bean id="userService" class="com.example.UserService"/>
Bean 특징
- 싱글톤 기반 : 동일한 Bean을 여러 곳에서 공유
- Spring이 생성/소멸 관리
- 초기화 시점 : 생성 -> 의존성 주입 ->
@PostConstruct호출
@PostConstruct: Spring Bean이 생성되고 의존성(DI)를 모두 주입 받은 직후 호출
- 소멸 시점 :
@PreDestroy호출
@PreDestroy: Spring 컨테이너가 종료될 떄 자동 호출되며, Bean이 컨테이너에서 제거되기 직전 호출.
- DI가능 : 다른 Bean을 의존성으로 주입 받을 수 있음
Spring이 Bean을 생성, 관리, 소멸, 의존성 주입(DI)까지 담당하는 객체 저장소
Spring이 모든 Bean을 모아두고 관리하는 공간
역할
- Bean의 생성, 초기화, 소멸 관리
- 의존성 주입(DI)
- Bean 검색/조회
- Bean Scope 관리 (Singleton, Prototype 등)
- BeanFactory
- Spring의 최초 Bean 컨테이너
- 지연 로딩(Lazy Loading) 방식
- Bean 을 실제로 요청할 때 생성
- 단순하고 가볍지만, 많은 기능이 부족
- 거의 직접 사용하지 않고, 내부적으로 ApplicationContext가 BeanFactory 기능을 포함
- ApplicationContext
- BeanFactory를 확장한 완전한 컨테이너
- 즉시 로딩(Eager Loading) 방식
- 컨테이너 시작 시점에 Bean 생성
- 애플리케이션 전체 기능 제공 (이벤트, 메시지, AOP 지원 등)
- 싱글톤 기본 : ApplicationContext는 기본적으로 Singleton Bean관리
- DI 지원 : 컨테이너가 필요한 Bean을 자동으로 주입
- 생명주기 관리 : Bean 생성 -> DI -> @PostConstruct -> 사용 -> @PreDestroy
- Bean Scope관리 : Singleton, Prototype, Request, Session 등
싱글톤(Singleton): 컨테이너 당 1개만 생성 (기본값)
- 컨테이너 종료 시 destroy 호출
프로토타입(Prototype): 요청 시마다 각 새 객체 생성
- Spring이 생성만 하고 관리하지 않기 때문에 destroy메서드 호출 안됨 -> 직접 정리 필요
요청(Request): HTTP 요청 시마다 새 객체 (웹전용)세션(Session): HTTP 세션 당 객체 하나 (웹전용)
@PostConstruct 또는 InitializingBean)@PreDestroy 또는 DisposableBean)DB 작업을 하나의 원자적 단위로 묶어서 처리
여러 작업을 하나로 묶어서 모두 성공하거나 모두 실패하도록 처리
실패 시 모든 작업 롤백 가능 -> 데이터 일관성 유지
ACID
Atomicity(원자성): 모든 작어비 성공하거나 실패Consistency(일관성): 트랜잭션 수행 후 데이터 무결성 보장Isolation(독립성): 동시에 수행되는 트랜잭션간 간섭 방지Durability(지속성): 커밋 후 데이터는 영구 저장
트랜잭션 처리 방법
- 선언전 트랜잭션
예시
@Service public class UserService { @Transactional public void registerUser(User user) { userRepository.save(user); mailService.sendWelcomeEmail(user); } }
@Transactional을 붙이면 Spring이 AOP를 이용해 트랜잭션 처리
- 장점
- 코드 간결 -> 트랜잭션 시작/커밋/롤백 자동 처리
- 비즈니스 로직에 집중 가능
- Rollback 정책
- 기본 : Unchecked Exception(RuntimeException, Error) 발생 시 롤백
- Checked Exception도 롤백 가능 :
@Transactional(rollbackFor = Exception.class)
- 전파(Propagation)
- 전파 : 트랜잭션이 다른 트랜잭션 안에서 호출될 때 동작 방식
REQUIRED: 기존 트랜잭션이 있으면 참여, 없으면 새로 생성REQUIRES_NEW: 항상 새 트랜잭션 생성, 기존 트랜잭션과 독립SUPPORTS: 트랜잭션이 있으면 참여, 없으면 그냥 실행NOT_SUPPORTED: 트랜잭션 있으면 잠시 중단 후 실행MANDATORY: 트랜잭션 있어야 함, 없으면 예외NEVER: 트랜잭션 있으면 제외NESTED: 기존 트랜잭션 내에서 중첩 트랜잭션 실행 가능
트랜잭션 동작 원리(Spring)
- @Transactional 적용 메서드 호출
- AOP 프록시가 트랜잭션 시작
- 메서드 실행
- 정상 종료 -> Commit
- 예외 발생 -> Rollback (Unchecked Exception 기본)
- 트랜잭션 종료
Spring 트랜잭션 특징
- DB 작업뿐만 아니라 다른 리소스 연계 가능
- JMS, 메시지 큐, 파일 작업 등도 트랜잭션으로 묶어서 처리 가능
- 프록시 기반 동작
- JDK 동적 프록시 : 인터페이스 기반, 인터페이스 타입으로 Bean 호출해야 AOP적용
- CGLIB : 클래스 상속 기반, 클래스 타입으로 호출 가능(인터페이스 필요 없음)
- 프로그래밍 방식(직접 트랜잭션 관리) vs 선언전 트랜잭션
- 선언적(
@Transactional) 권장, 코드가 간결하고 유지보수 용이
- 내부 호출은 같은 트랜잭션 안에서는 실행됨
- 별도의 트랜잭션 적용이 안 됨
프록시(Proxy)
- 대상 객체(실제 객체)를 감싸는 대리 객체
- 대리 객체를 통해 원래 객체의 메서드를 호출하면서 추가 기능 수행 가능
- Spring 트랜잭션과 프록시
@Transactional붙은 메서드를 호출하면 Spring이 프록시 객체 생성실제 호출 흐름
- 클라이언트 -> 프록시 호출
- 프록시 -> 트랜잭션 시작
- 실제 Bean 메서드 호출
- 메서드 정상 종료 -> 커밋
- 예외 발생 -> 롤백
- 주의점
- 같은 클래스 내부 호출 시 프록시를 거치지 않음
@Transactional public void methodA() { methodB(); // 같은 클래스 내부 호출 → 트랜잭션 적용 안 됨 }
- 프록시 객체로 호출해야
@Transactional이 정상 동작
공통 관심사항을 비지니스 로직과 분리
예 : 로깅, 트랜잭션, 인증/인가, 성능 모니터링
동작 원리
- Spring이 Proxy 객체 생성
- JDK Proxy : 인터페이스 기반
- CHLIB : 클래스 기반 (인터페이스 없어도 가능)
- Advice : 공통로직 코드
- JoinPoint : 공통로직 적용 위치
- Pointcut : 공통로직 적용 조건
Proxy기반에서는 클래스 내부 메서드 호출은 AOP 적용 안됨
- 자기 자신 메서드 호출은 프록시를 거치지 않아서 AOP적용 안됨